package main

import (
	"Goltach/dat2img"
	"context"
	"encoding/base64"
	"fmt"
	"net"
	"net/http"
	"net/rpc"
	"os"
	"os/exec"
	"path"
	"syscall"
	"time"
)

// App struct
type App struct {
	ctx         context.Context
	rpc_address string
	rpc_network string
	rpc_client  *rpc.Client
	cmd_chatlog *exec.Cmd
}

// NewApp creates a new App application struct
func NewApp() *App {
	return &App{}
}

// startup is called when the app starts. The context is saved
// so we can call the runtime methods
func (a *App) startup(ctx context.Context) {
	a.ctx = ctx
}

// Greet returns a greeting for the given name
func (a *App) Greet(name string) string {
	return fmt.Sprintf("Hello %s, It's show time!", name)
}
func (a *App) GetFreePort() (int, error) {
	addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
	if err != nil {
		return 0, err
	}

	l, err := net.ListenTCP("tcp", addr)
	if err != nil {
		return 0, err
	}

	defer l.Close()
	return l.Addr().(*net.TCPAddr).Port, nil
}

func (a *App) ConnectChatlog(network string, address string) Reply[string] {
	var rpc_client *rpc.Client
	for i := 0; i < 5; i += 1 {
		time.Sleep(1 * time.Second)
		client, err := rpc.DialHTTP(network, address)
		print_err("App::StartChatlog", err)
		if client != nil {
			rpc_client = client
			break
		}
	}
	if rpc_client == nil {
		return reply("", "[App::StartChatlog] RPC Connection failed")
	}
	a.rpc_network = network
	a.rpc_address = address
	a.rpc_client = rpc_client
	return reply("", "")
}

func (a *App) StartChatlog(showConsole bool) Reply[string] {
	if a.cmd_chatlog != nil {
		return reply("already start", "")
	}
	path, look_path_err := exec.LookPath("./chatlog/chatlog")
	if look_path_err != nil {
		return reply("", "[App::StartChatlog] LookPath failed, err: "+look_path_err.Error())
	}

	port, err := a.GetFreePort()
	if err != nil {
		return reply("", "[App::StartChatlog] GetFreePort failed, err: "+err.Error())
	}

	rpc_network := "tcp"
	rpc_address := fmt.Sprintf("127.0.0.1:%d", port)
	cmd := exec.Command(path, "-r", rpc_network, "-d", rpc_address)
	if showConsole {
		cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
	}

	err = cmd.Start()
	if err != nil {
		return reply("", "[App::StartChatlog] Command.Start failed, err: "+err.Error())
	}

	connection_reply := a.ConnectChatlog(rpc_network, rpc_address)
	if connection_reply.Error != "" {
		cmd.Process.Kill()
		return connection_reply
	}

	a.cmd_chatlog = cmd
	go func() {
		cmd.Wait()
		if cmd.ProcessState != nil && cmd.ProcessState.Exited() {
			fmt.Printf("进程已结束，退出码: %d\n", cmd.ProcessState.ExitCode())
		}
		if cmd == a.cmd_chatlog {
			a.StopChatlog()
		}
	}()

	return reply("", "")
}
func print_err(func_name string, err error) {
	if err != nil {
		msg := "[" + func_name + "] err: " + err.Error()
		println(msg)
	}
}
func (a *App) StopChatlog() Reply[string] {
	if a.rpc_client != nil {
		err := a.rpc_client.Close()
		print_err("App::StartChatlog", err)
	}
	if a.cmd_chatlog != nil {
		err := a.cmd_chatlog.Process.Kill()
		print_err("App::StartChatlog", err)
	}
	a.rpc_client = nil
	a.cmd_chatlog = nil
	a.rpc_network = ""
	a.rpc_address = ""
	return reply("", "")
}
func (a *App) GetIsChatlogReady() Reply[bool] {
	return reply(a.rpc_client != nil, "")
}

// Account 表示一个微信账号
type Account struct {
	Name        string `json:"name"`
	Platform    string `json:"platform"`
	Version     int    `json:"version"`
	FullVersion string `json:"fullVersion"`
	DataDir     string `json:"dataDir"`
	Key         string `json:"key"`
	PID         uint32 `json:"pid"`
	ExePath     string `json:"exePath"`
	Status      string `json:"status"`
}
type Config struct {
	ConfigDir   string          `mapstructure:"-"`
	LastAccount string          `mapstructure:"last_account" json:"last_account"`
	History     []ProcessConfig `mapstructure:"history" json:"history"`
}

type ProcessConfig struct {
	Type        string `mapstructure:"type" json:"type"`
	Account     string `mapstructure:"account" json:"account"`
	Platform    string `mapstructure:"platform" json:"platform"`
	Version     int    `mapstructure:"version" json:"version"`
	FullVersion string `mapstructure:"full_version" json:"full_version"`
	DataDir     string `mapstructure:"data_dir" json:"data_dir"`
	DataKey     string `mapstructure:"data_key" json:"data_key"`
	WorkDir     string `mapstructure:"work_dir" json:"work_dir"`
	HTTPEnabled bool   `mapstructure:"http_enabled" json:"http_enabled"`
	HTTPAddr    string `mapstructure:"http_addr" json:"http_addr"`
	LastTime    int64  `mapstructure:"last_time" json:"last_time"`
	Files       []File `mapstructure:"files" json:"files"`
}

type File struct {
	Path         string `mapstructure:"path" json:"path"`
	ModifiedTime int64  `mapstructure:"modified_time" json:"modified_time"`
	Size         int64  `mapstructure:"size" json:"size"`
}

type Reply[T any] struct {
	Data  T      `json:"data"`
	Error string `json:"error"`
}

type Keys struct {
	DataKey string `json:"data_key"`
	ImgKey  string `json:"img_key"`
}

func (a *App) DecryptDBFiles() Reply[string] {
	return rpc_reply(a, "App.DecryptDBFiles", "")
}
func (a *App) GetWXInstances() Reply[[]Account] {
	return rpc_reply(a, "App.GetWXInstances", make([]Account, 0))
}
func (a *App) StartService() Reply[string] {
	return rpc_reply(a, "App.StartService", "")
}
func (a *App) StopService() Reply[string] {
	return rpc_reply(a, "App.StopService", "")
}
func (a *App) StartAutoDecrypt() Reply[string] {
	return rpc_reply(a, "App.StartAutoDecrypt", "")
}
func (a *App) StopAutoDecrypt() Reply[string] {
	return rpc_reply(a, "App.StopAutoDecrypt", "")
}
func (a *App) RefreshSession() Reply[string] {
	return rpc_reply(a, "App.RefreshSession", "")
}
func (a *App) GetAccount() Reply[string] {
	return rpc_reply(a, "App.GetAccount", "")
}
func (a *App) GetPlatform() Reply[string] {
	return rpc_reply(a, "App.GetPlatform", "")
}
func (a *App) GetWXVersion() Reply[int] {
	return rpc_reply(a, "App.GetWXFullVersion", 0)
}
func (a *App) GetWXFullVersion() Reply[string] {
	return rpc_reply(a, "App.GetWXFullVersion", "")
}
func (a *App) GetDataDir() Reply[string] {
	return rpc_reply(a, "App.GetDataDir", "")
}
func (a *App) GetDataKey() Reply[string] {
	return rpc_reply(a, "App.GetDataKey", "")
}
func (a *App) GetDataUsage() Reply[string] {
	return rpc_reply(a, "App.GetDataUsage", "")
}
func (a *App) GetWorkDir() Reply[string] {
	return rpc_reply(a, "App.GetWorkDir", "")
}
func (a *App) GetWorkUsage() Reply[string] {
	return rpc_reply(a, "App.GetWorkUsage", "")
}
func (a *App) GetHTTPEnabled() Reply[bool] {
	return rpc_reply(a, "App.GetHTTPEnabled", false)
}
func (a *App) GetHTTPAddr() Reply[string] {
	return rpc_reply(a, "App.GetHTTPAddr", "")
}
func (a *App) GetAutoDecrypt() Reply[bool] {
	return rpc_reply(a, "App.GetAutoDecrypt", false)
}
func (a *App) GetLastSession() Reply[int64] {
	return rpc_reply[int64](a, "App.GetLastSession", 0)
}
func (a *App) GetWXStatus() Reply[string] {
	return rpc_reply(a, "App.GetWXStatus", "")
}
func (a *App) GetHistory() Reply[map[string]ProcessConfig] {
	return rpc_reply(a, "App.GetHistory", make(map[string]ProcessConfig))
}
func (a *App) GetImgKey() Reply[string] {
	return rpc_reply(a, "App.GetImgKey", "")
}
func (a *App) SearchKeys() Reply[Keys] {
	result := Keys{}
	return rpc_reply(a, "App.SearchKeys", result)
}

type FileStat struct {
	IsDir bool   `json:"is_dir"`
	Name  string `json:"name"`
	Size  int64  `json:"data"`
}

func (a *App) GetFileStat(path string) Reply[FileStat] {
	data := FileStat{
		IsDir: false,
		Name:  "",
		Size:  0,
	}
	if stat, err := os.Stat(path); err != nil {
		data.IsDir = stat.IsDir()
		data.Name = stat.Name()
		data.Size = stat.Size()
		return reply(data, "")
	} else {
		return reply(data, "file not exists")
	}
}

func reply[T any](data T, msg string) Reply[T] {
	return Reply[T]{
		Data:  data,
		Error: msg,
	}
}
func rpc_reply[T any](a *App, method string, default_data T) Reply[T] {
	if a.rpc_client == nil {
		return reply(default_data, "RPC no ready yet.")
	}
	reply := Reply[T]{
		Data:  default_data,
		Error: "",
	}

	err := a.rpc_client.Call(method, &struct{}{}, &reply.Data)
	if err != nil {
		reply.Error = "" + method + ", err:" + err.Error()
	}
	return reply
}

type Base64Image struct {
	Data     string `json:"data"`
	MimeType string `json:"mimeType"`
	Ext      string `json:"ext"`
}

func (a *App) DecryptImage(aeskey string, src string) Reply[Base64Image] {
	b, err := os.ReadFile(src)
	ret := Base64Image{}
	if err != nil {
		return reply(ret, err.Error())
	}
	dat2img.ScanAndSetXorKey(path.Dir(src))
	dat2img.SetAesKey(aeskey)
	out, ext, err := dat2img.Dat2Image(b)

	if err != nil {
		return reply(ret, err.Error())
	}
	ret.MimeType = http.DetectContentType(out)
	ret.Data = "data:" + ret.MimeType + ";base64," + base64.StdEncoding.EncodeToString(out)
	ret.Ext = ext
	return reply(ret, "")
}
